/**
 * 20210810 created by jordia
 * 20210817:对外暴露原生合成器实例（entity），以便对渲染结果和各个通道进行操作
 */

import {Color,MeshBasicMaterial,Vector2,ShaderMaterial} from 'three';
import {EffectComposer} from "three/examples/jsm/postprocessing/EffectComposer";
import {RenderPass} from "three/examples/jsm/postprocessing/RenderPass";
import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass';
import {UnrealBloomPass} from "three/examples/jsm/postprocessing/UnrealBloomPass";

const DARK_COLOR=new Color('#000000');
const DARK_MATERIAL=new MeshBasicMaterial({color:DARK_COLOR});
const DARKEN_OBJ=(obj,filter,isMask)=>{
    if(!obj.material)return;
    if(isMask){
        if(filter.findIndex((o)=>o.uuid===obj.uuid)<0){
            obj.userData.material=obj.material;
            obj.material=DARK_MATERIAL;
        }
    }else{
        if(filter.findIndex((o)=>o.uuid===obj.uuid)<0) {
            obj.material = obj.userData.material;
        }
    }
};

/**
 * 可选择的光晕效果
 * @param camera {Camera} 相机
 * @param renderer {WebGLRenderer} 渲染器
 * @param scene {Scene} 场景
 * @param values {object} 可选参数对象：
 *      layerSelected {number}:渲染光晕所在的层
 *      strength {number}:光晕强度
 *      radius {number}:光晕大小
 *      threshold {number}:阈值
 *      resolution {Vector2}:解析度
 *
 * 示例：
        composer=new UnrealBloomSelective(camera,renderer,scene,{
            threshold:0,
            strength:2,
            radius:0.2,
            layerSelected:1,
        });
        composer.onBeforeRender=()=>{
            let size=new Vector2();
            renderer.getSize(size);
            renderer.setViewport(0,0,size.x*0.5,size.y*0.5);
        };
        composer.select(meshes[0]);
        let t=0;
        setInterval(()=>{
            composer.unselect(meshes[t]);
            t++;
            t%=10;
            composer.select(meshes[t]);
        },1000);
 */
export default class UnrealBloomSelective{
    constructor(camera,renderer,scene,values={}){
        this.cam=camera;
        this.rdr=renderer;
        this.scn=scene;
        this.values=values;
        this.init();
    }

    //.........................................public...........................................

    get entity(){
        return this.bloomComp;
    }

    /**
     * 渲染光晕所在的层（如果要重置对象的层，需自行手动处理，这里不对层做更改）
     * @param num
     */
    set layerSelected(num){
        this.layerS=num;
    }

    set strength(num){
        this.str=Math.max(num,0);
        this.bloomPas.strength=this.str;
        this.bloomPas.compositeMaterial.uniforms[ "bloomStrength" ].value = this.str;
    }
    get strength(){
        return this.str;
    }

    set radius(num){
        this.rad=Math.max(num,0);
        this.bloomPas.radius=this.rad;
        this.bloomPas.compositeMaterial.uniforms[ "bloomRadius" ].value = this.rad;
    }
    get radius(){
        return this.rad;
    }

    set threshold(num){
        this.thres=Math.max(num,0);
        this.bloomPas.threshold=this.thres;
        this.bloomPas.highPassUniforms[ "luminosityThreshold" ].value = this.thres;
    }
    get threshold(){
        return this.thres;
    }

    /**
     * 渲染前执行函数
     * @param func
     */
    set onBeforeRender(func){
        this.beforeRender=func;
    }

    /**
     * 渲染后执行函数
     * @param func
     */
    set onAfterRender(func){
        this.afterRender=func;
    }

    /**
     * 选择对象
     * @param objs
     */
    select(...objs){
        for(let obj of objs){
            let idx=this.selected.findIndex((o)=>o.uuid===obj.uuid);
            if(idx<0)this.selected.push(obj);
        }
    }

    /**
     * 移除已选择对象
     * @param objs
     */
    unselect(...objs){
        for(let obj of objs){
            let idx=this.selected.findIndex((o)=>o.uuid===obj.uuid);
            if(idx<0)continue;
            this.selected.splice(idx,1);
        }
    }

    /**
     * 清除选择
     */
    clear(){
        this.unselect(...this.selected);
    }

    /**
     * 设置渲染器尺寸
     * @param width
     * @param height
     */
    setSize(width,height){
        this.bloomComp.setSize(width,height);
        this.finalComp.setSize(width,height);
        this.render();
    }

    /**
     * 渲染函数
     */
    render(){
        if(this.beforeRender)this.beforeRender();
        this.cam.layers.enable(this.layerS);
        for(let obj of this.selected){
            obj.layers.enable(this.layerS);
        }
        this.scn.traverse((c)=>{
            DARKEN_OBJ(c,this.selected,true);
        });
        this.scn.userData.background=this.scn.background;
        this.scn.background=DARK_COLOR;
        this.bloomComp.render();
        this.scn.background=this.scn.userData.background;
        this.scn.traverse((c)=>{
            DARKEN_OBJ(c,this.selected,false);
        });

        this.cam.layers.disable(this.layerS);
        for(let obj of this.selected){
            obj.layers.disable(this.layerS);
        }
        this.finalComp.render();
        if(this.afterRender)this.afterRender();
    }

    //.........................................private...........................................

    init(){
        this.initVariable();
        this.initComponents();
    }
    initVariable(){
        this.selected=[];
        this.layerS=this.values.hasOwnProperty('layerSelected')?this.values.layerSelected:1;
        this.str=this.values.hasOwnProperty('strength')?this.values.strength:1;
        this.rad=this.values.radius||0.2;
        this.thres=this.values.hasOwnProperty('threshold')?this.values.threshold:0.5;
        this.res=this.values.resolution||new Vector2(256,256);
        this.bloomComp=null;
        this.renderPas=null;
        this.bloomPas=null;
        this.finalComp=null;
        this.finalPas=null;
        this.beforeRender=null;
        this.afterRender=null;
    }
    initComponents(){
        this.initBloom();
        this.initFinal();
    }
    initBloom(){
        this.bloomComp=new EffectComposer(this.rdr);
        this.renderPas=new RenderPass(this.scn,this.cam);
        this.bloomPas=new UnrealBloomPass(this.res,this.str,this.rad,this.thres);
        this.bloomComp.addPass(this.renderPas);
        this.bloomComp.addPass(this.bloomPas);
        this.bloomComp.renderToScreen=false;
    }
    initFinal(){
        this.finalComp=new EffectComposer(this.rdr);
        this.finalPas=new ShaderPass(new ShaderMaterial({
            uniforms:{
                baseTexture:{value:null},
                addTexture:{value:this.bloomComp.renderTarget2.texture}
            },
            vertexShader:`
                varying vec2 vUv;
                void main() {
                    vUv = uv;
                    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
                }
            `,
            fragmentShader:`
                uniform sampler2D baseTexture;
                uniform sampler2D addTexture;
                varying vec2 vUv;
                void main() {
                    gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( addTexture, vUv ) );
                }
            `
        }),'baseTexture');
        this.finalPas.needsSwap=true;
        this.finalComp.addPass(this.renderPas);
        this.finalComp.addPass(this.finalPas);
    }
}